
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sychip.fhc.app.todo


//import androidx.biometric.BiometricManager
//import androidx.biometric.BiometricPrompt
//import no.nordicsemi.android.kotlin.ble.scanner.aggregator.BleScanResultAggregator
//import no.nordicsemi.android.kotlin.ble.scanner.errors.ScanFailedError
//import no.nordicsemi.android.kotlin.ble.scanner.errors.ScanningFailedException
//import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResults
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.viewModelScope
import com.sychip.fhc.APP
import com.sychip.fhc.AppType
import com.sychip.fhc.MainApplication
import com.sychip.fhc.app.data.repo.BleDeviceRepo
import com.sychip.fhc.app.data.source.dto.BleDeviceDto
import com.sychip.fhc.app.pref.ACMode
import com.sychip.fhc.app.pref.ACStatus
import com.sychip.fhc.app.pref.PrefDataStoreIf
import com.sychip.fhc.base.BaseAppViewModel
import com.sychip.fhc.lib.mqtt.AwsMqtt
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended
import org.eclipse.paho.client.mqttv3.MqttClient
import org.eclipse.paho.client.mqttv3.MqttMessage
import timber.log.Timber
import javax.inject.Inject

data class AlertState(
    var showDialog: Boolean = false,
    var title: String = "",
    var text: String = "",
    var confirmButton: String= "",
    var dismissButton: String= "",
)


/**
 * ViewModel for the Start screen.
 */
@HiltViewModel
class StartViewModel @Inject constructor(
    app : MainApplication,
    private val userRepo: BleDeviceRepo,
    private val acDataStoreRepo: PrefDataStoreIf
) : BaseAppViewModel(app) {

    var awsMqtt: AwsMqtt?  =  null
    var mqttClient: MqttClient? = null
    init {
//        if(APP.TYPE == AppType.WSN) {
//            MainApplication.instance().showBottomNavbar(false)
//        }
    }
    override fun firstInitFinished() {
        super.firstInitFinished()
        if(APP.TYPE == AppType.AcControl) {
            reconnectAWS()
        }
//        else if(APP.TYPE == AppType.WSN) {
////            MainApplication.instance().showBottomNavbar(true)
//        }
    }

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   Business Test Flow Sample  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    sealed interface UiTestState {
        object Null : UiTestState
        object Loading : UiTestState
        data class Success(val data: List<BleDeviceDto>) : UiTestState
        data class Error(val throwable: Throwable? = null) : UiTestState

    }

//    data class UiTestExtData(val str1:String)

    private val _testFlow = MutableStateFlow<UiTestState>(UiTestState.Null)
    private val _testData : MutableList<BleDeviceDto>  =  mutableStateListOf<BleDeviceDto>()
    val testFlow: StateFlow<UiTestState> = _testFlow.asStateFlow()

    fun loadTestData() {
        // 比下方collect写法更能处理异常
        // 还有cancel ensureActive，cancelable等写法比较实用
        userRepo.getAllBleDevices().map { result ->
            //map不是必须，我们可以在里面进行转换数据，然后 onEach....，如果不需要转换，可以移除map结构
            //UiTestState.Success(result) as UiTestState
            result
        }.onCompletion { cause->

        }.onEach {result ->
            _testData.addAll(result)
            _testFlow.update { UiTestState.Success( data = _testData ) }
        }.catch { error->
            _testFlow.update { UiTestState.Error(error) }
        }.onStart {
            _testFlow.update { UiTestState.Loading }
        }.launchIn(viewModelScope)





//        _testFlow.update { UiTestState.Loading }
//        viewModelScope.launch{
//            userRepo.getUsers().catch { error->
//                _testFlow.update { UiTestState.Error(error) }
//            }.collect { result ->
//                _testData.addAll(result)
//                _testFlow.update { UiTestState.Success( data = _testData ) }
//            }
//        }
    }



    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  AlertState 或某种无状态的 Business数据  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    private val _alertState = MutableStateFlow(AlertState(title="AlertState INIT"))
    val alertState: StateFlow<AlertState> = _alertState.asStateFlow()

    fun showDialog(show: Boolean) =  viewModelScope.launch {
        _alertState.update {
            it.copy(showDialog = show)
        }
    }

    fun alertInfo(title: String, text: String) =  viewModelScope.launch {
        _alertState.update {
            it.copy(
                title = title,
                        text = text
            )
        }
    }

    fun loadDialog() {
        _alertState.update {
            it.copy(showDialog = true, title = "0000000000000")
        }
        viewModelScope.launch {
            delay(2000)
            _alertState.update {
                it.copy(showDialog = true, title = "aaaaaaaaaaa")
            }
        }
    }



//    private fun showBiometricPromptForEncryption(fragment: Fragment) {
//        val canAuthenticate = BiometricManager.from(app).canAuthenticate()
//        if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
//            val secretKeyName = SECRET_KEY_NAME
//            cryptographyManager = CryptographyManager()
//            val cipher = cryptographyManager.getInitializedCipherForEncryption(secretKeyName)
//            val biometricPrompt =
//                BiometricPromptUtils.createBiometricPrompt(fragment, ::encryptAndStoreServerToken)
//            val promptInfo = BiometricPromptUtils.createPromptInfo(fragment)
//            biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
//        }
//    }


    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   Business  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    sealed class PageAsync<out T> {
        object Loading : PageAsync<Nothing>()
        data class Error(val errorMessage: Int) : PageAsync<Nothing>()
        data class Success<out PageTask>(val data: PageTask) : PageAsync<PageTask>()
    }
    data class PageTask(
        var isLoading: Boolean = true,  //用来区分画面是否初始化
        val errMessage: Int? = null,
        val data: MutableList<String>  =  mutableStateListOf<String>(),
        var pageIndex: Int = 0,
    )

    val pageTask: StateFlow<PageTask> = MutableStateFlow(PageTask(false)).asStateFlow()

    fun generateFakeData(page: Int): List<String> {
        return List(20) { "Item ${(page - 1) * 20 + it + 1}" }
    }
    fun addPage( ) =  viewModelScope.launch {

        Timber.i("2222   page:%s   %s   %s", pageTask.value.pageIndex, pageTask.value.data.size, pageTask.value.isLoading)
        pageTask.value.isLoading = true
        delay(2000)
        pageTask.value.pageIndex += 1
        pageTask.value.data.addAll(generateFakeData(pageTask.value.pageIndex + 1))
        pageTask.value.isLoading = false
//        MainApplication.instance().getAppViewModel().setNameAge("aaaaaa", 13)
        _toastMsg.value = R.string.murata_name
    }




    fun toastMessageShown() {
        _toastMsg.value = null
        _toastMsgArg.value = emptyArray<Any>()
    }

//    fun refresh() {
//        _isLoading.value = true
//        viewModelScope.launch {
//            _isLoading.value = false
//        }
//    }


//
//    private var filters: List<BleScanFilter> = emptyList()
//
//    // This can't be observed in View Model Scope, as it can exist even when the
//    // scanner is not visible. Scanner state stops scanning when it is not observed.
//    // .stateIn(viewModelScope, SharingStarted.Lazily, ScanningState.Loading)
//    private fun List<BleScanResults>.applyFilters(config: List<BleScanFilterState>) =
//        filter { result ->
//            config.all {
//                it.predicate(it.selected, result)
//            }
//        }
//
//    fun setFilters(filters: List<BleScanFilter>) {
//        this.filters = filters
//        this._filterConfig.update {
//            filters.map {
//                BleScanFilterState(it.title, it.initiallySelected, it.filter)
//            }
//        }
//    }
//
//    fun toggleFilter(index: Int) {
//        this._filterConfig.value = mutableListOf<BleScanFilterState>()
//            .apply { addAll(_filterConfig.value) }
//            .apply {
//                this[index] = this[index].copy(selected = !this[index].selected)
//            }
//    }
//
//    private val _filterConfig = MutableStateFlow<List<BleScanFilterState>>(emptyList())
//     val filterConfig = _filterConfig.asStateFlow()
//
//    private var currentJob: Job? = null
//
//    private val _state = MutableStateFlow<BleScanningState>(BleScanningState.Loading)
//    val state = _state.asStateFlow()
//
//    private fun relaunchScanning() {
//        currentJob?.cancel()
//        val aggregator = BleScanResultAggregator()
//        currentJob = scannerRepository.getScannerState()
//            .map { aggregator.aggregate(it) }
//            .filter { it.isNotEmpty() }
//            .combine(filterConfig) { result, config  ->
//                result.applyFilters(config)
//            }
//            .onStart { _state.value = BleScanningState.Loading }
//            .cancellable()
//            .onEach {
//                // To prevent lags on the device list only refresh the list when
//                // it has changed. This simple implementation just checks if
//                // any new device was found, which isn't the best, as devices may change
//                // advertising data and this won't be shown until some new device is found.
//                val shouldRefresh = when (val list = _state.value) {
//                    is BleScanningState.DevicesDiscovered -> list.devices.size != it.size
//                    else -> true
//                }
//                if (shouldRefresh) {
//                    _state.value = BleScanningState.DevicesDiscovered(it)
//                }
//            }
//            .catch { e ->
//                _state.value = (e as? ScanningFailedException)?.let {
//                    BleScanningState.Error(it.errorCode.value)
//                } ?: BleScanningState.Error(ScanFailedError.UNKNOWN.value)
//            }
//            .launchIn(viewModelScope)
//    }

//    private val _nfcStatus = MutableStateFlow<NFCBleStatus>(NFCBleStatus())
//    val nfcStatus: StateFlow<NFCBleStatus> = _nfcStatus.asStateFlow()
//
//    fun toastMessageShown() {
//        _toastMsg.value = null
//        _toastMsgArg.value = emptyArray<Any>()
//    }

    private val _acStatus = MutableStateFlow<ACStatus>(ACStatus(powerOn = false))
    val acStatus: StateFlow<ACStatus> = _acStatus.asStateFlow()

    private val _awsConnected = MutableStateFlow<Boolean>( false)
    val awsConnected: StateFlow<Boolean> = _awsConnected.asStateFlow()


//    private val _toastMsg = MutableStateFlow<String>("")
//    val toastMsg: StateFlow<String> = _toastMsg.asStateFlow()
    val TOPIC ="murata_iot/example/topic"
    val TOPIC_BABY_CRY ="murata_baby/example/topic"
    val devId= "111"
    fun  reconnectAWS(){
        _testFlow.update { UiTestState.Loading }
        acDataStoreRepo.getACStatus(devId).map { result ->
            if (result != null) {
                _acStatus.value = result
            }
        }.launchIn(viewModelScope)

        CoroutineScope(Dispatchers.IO).launch {
            if(mqttClient != null){
                if(mqttClient!!.isConnected){
                    mqttClient!!.unsubscribe(TOPIC)
                    mqttClient!!.unsubscribe(TOPIC_BABY_CRY)
                    mqttClient!!.disconnect()
                }
                _awsConnected.value = false
                mqttClient = null
                awsMqtt?.clearCallback()
                awsMqtt = null
            }
            awsMqtt =  AwsMqtt(context)
            awsMqtt?.addCallback(object : MqttCallbackExtended{
                override fun connectionLost(cause: Throwable?) {
                    _awsConnected.value = false
                    _testFlow.update { UiTestState.Error( cause ) }
                }
                override fun messageArrived(topic: String?, message: MqttMessage?) {
                    if(topic != null && message != null && (topic == TOPIC || topic == TOPIC_BABY_CRY)) onACStatusUpdate(message)
                }
                override fun deliveryComplete(token: IMqttDeliveryToken?) {}

                override fun connectComplete(reconnect: Boolean, serverURI: String?) {
                    _awsConnected.value = true
                    _testFlow.update { UiTestState.Success(data = emptyList()) }
                    subscribeMqtt()
                }
            })
            mqttClient = awsMqtt?.connect()
        }
    }

    fun subscribeMqtt(){
        mqttClient?.subscribe(TOPIC, 1) { topic1: String, message: MqttMessage ->
            onACStatusUpdate(message)
        }
        mqttClient?.subscribe(TOPIC_BABY_CRY, 1) { topic1: String, message: MqttMessage ->
            onBabyCryUpdate(message)
        }
    }
    fun onBabyCryUpdate(message: MqttMessage?){
        val payload = if(message== null) "TEST" else  String(message.payload)
        if(payload.isEmpty()) return
        showDialog(false)
        alertInfo("Info", "Baby is crying\n($payload)")
        showDialog(true)
    }

    fun onACStatusUpdate(message: MqttMessage){
        val payload = String(message.payload)
        if(payload.isNotEmpty()){
            _toastMsg.value = R.string.aws_msg
            _toastMsgArg.value = arrayOf(payload)
        }
        val power = "power:"
        val mode = "mode:"
        val temp = "temp:"
        val datas = payload.split("|")
        Timber.i(payload)
        datas.forEach{item->
            try {
                if(item.startsWith(power)){
                    val powerVal = item.substring(power.length)
                    _acStatus.value.powerOn = powerVal == "on"
                }else if(item.startsWith(mode)){
                    val modeVal = item.substring(mode.length)
                    if(modeVal == "auto"){
                        _acStatus.value.mode = ACMode.AUTO
                    }else if(modeVal == "cooling"){
                        _acStatus.value.mode = ACMode.COOL
                    }else if(modeVal == "heating"){
                        _acStatus.value.mode = ACMode.HEAT
                    }
                    _acStatus.value.powerOn = true
                }else if(item.startsWith(temp)){
                    val tempVal = item.substring(temp.length)
                    _acStatus.value.temp = tempVal.toInt()
                    _acStatus.value.powerOn = true
                }

                viewModelScope.launch {
                    acDataStoreRepo.saveACStatus(devId, _acStatus.value)
                }
            }catch (e: Exception) {
                _toastMsg.value = R.string.err_parse_error
                _toastMsgArg.value = arrayOf("Mqtt Payload", e.message.toString())
                Timber.i(e)
            }

        }
        acDataStoreRepo.getACStatus(devId).map { result ->
            Timber.i("    >>>>>>>>>>>>>>>>>>>>>>>>> 2222222222222222: ${result?.powerOn}   temp: ${result?.temp}")
            if (result != null) {
                _acStatus.value = result
            }
        }.launchIn(viewModelScope)
    }

}
